#include "cgzCHRedis.h"
#include "cgzCThreadPool.h"
#include <hiredis/hiredis.h>

static const char *cmds[] = {
    "SELECT",
    "GET",
    "SET",
    "DEL",
    "HGET",
    "HMGET",
    "HSET",
    "HSETNX",
    "HMSET",
    "HGETALL",
    "HDEL",
    "HEXISTS",
    "HLEN",
    "HKEYS",
    "HVALS",
    "HSCAN",
    "HINCRBY",
    "HINCRBYFLOAT",
    "HRANDFIELD",
    "HSTRLEN",
    "LPUSH",
    "RPUSH",
    "LPOP",
    "RPOP",
    "LRANGE",
    "LTRIM",
    "SADD",
    "SREM",
    "SPOP",
    "SMOVE",
    "SCARD",
    "SISMEMBER",
    "SMEMBERS",
    "SRANDMEMBER",
    "SDIFF",
    "SINTER",
    "SUNION",
    "SUNIONSTORE",
    "SINTERSTORE",
    "SDIFFSTORE",
    "ZADD",
    "ZREM",
    "ZINCRBY",
    "ZCARD",
    "ZRANGE",
    "ZRANGEBYSCORE",
    "ZRANK",
    "ZCOUNT",
    "ZSCORE",
    "ZREMRANGEBYRANK",
    "ZREMRANGEBYSCORE",
    "ZDIFF",
    "ZINTER",
    "ZUNION",
    "ZDIFFSTORE",
    "ZINTERSTORE",
    "ZUNIONSTORE",
    "XADD",
    "XREAD",
    "XGROUP",
    "XREADGROUP",
    "XACK",
    "XINFO",
    "XDEL",
    "XRANGE",
    "XREVRANGE",
    "XTRIM",
    "XCLAIM",
    "XPENDING",
    "XLEN",
    "TTL",
    "PTTL",
    "PERSIST",
    "EXPIRE",
    "PEXPIRE",
    "EXPIREAT",
    "PEXPIREAT",
    "TYPE",
    "KEYS",
    "RANDOMKEY",
    "MOVE",
    "RENAME",
    "SORT",
    "SCAN",
    "SCRIPT",
    "EVALSHA",
    "EVAL",
};

cgzCRedisReply::cgzCRedisReply(cgzRedisCode code)
    : m_code(code)
{
}

cgzCRedisReply::~cgzCRedisReply()
{
}

cgzCRedisCmd::cgzCRedisCmd(const cgzRedisCmdType cmd, const int cookid)
    : cgzCRedisReply(cgzRedisCode::EXEC_REQUEST),
      m_cmd(cmd),
      m_cookid(cookid),
      m_ret(false),
      m_err(false),
      m_reply(nullptr)

{
    m_args.clear();
}

cgzCRedisCmd::~cgzCRedisCmd()
{
    if (m_reply)
    {
        freeReplyObject(m_reply);
        m_reply = nullptr;
    }
    m_args.clear();
}

bool cgzRedisPingCheck(redisContext *c)
{
    if (!c)
    {
        return false; // 根本没有连接
    }

    const char *SPING = "PING";
    auto reply = (redisReply *)redisCommand(c, SPING);
    if (!reply)
    {
        return false;
    }

    bool ok = false;
    if (reply->type == REDIS_REPLY_STATUS)
    {
        if (0 == strcmp(reply->str, SPING))
        {
            ok = true;
        }
    }
    freeReplyObject(reply);
    return ok; // 连接有效
}

void cgzRedisCloseCheck(redisContext **c)
{
    if (*c)
    {
        redisFree(*c);
    }
    *c = nullptr;
}

cgzCHRedis::cgzCHRedis(
    cgzCThreadPool *pool,
    const char *name,
    const char *ip,
    const char *auth,
    const int port,
    const int idb,
    const int cid)
    : cgzITask(pool, name, cgzETask::CHRedis),
      m_ip(ip),
      m_auth(auth),
      m_port(port),
      m_idb(idb),
      m_cid(cid),
      m_firstAuth(false),
      m_redis(nullptr),
      m_argv{},
      m_argvlen{}

{
    // 连接部分
    connect();

    // 注册服务
    service();
}

cgzCHRedis::~cgzCHRedis()
{
    // 关闭连接
    cgzRedisCloseCheck(&m_redis);
}

void cgzCHRedis::authret()
{
    if (!m_firstAuth)
    {
        m_firstAuth = true;
        // 认证回调
        auto node = malloc(sizeof(cgzCRedisReply));
        auto acptC = new (node) cgzCRedisReply(cgzRedisCode::AUTH_SUCCESS);
        acptC->target = m_cid;
        acptC->origin = getId();
        retTask(acptC);
    }
}

bool cgzCHRedis::connect()
{
    // 创建 Redis 上下文
    m_redis = redisConnect(m_ip.c_str(), m_port);
    if (m_redis == nullptr || m_redis->err)
    {
        if (m_redis)
        {
            cgz_this_error("{}:{}", m_redis->errstr, m_name);
        }
        else
        {
            cgz_this_error("Can't allocate redis context:{}", m_name);
        }
        cgzRedisCloseCheck(&m_redis);
        return false;
    }

    if (m_auth.size() > 0)
    {
        redisReply *reply = (redisReply *)redisCommand(m_redis, "AUTH %s", m_auth.c_str());
        if (reply == nullptr)
        {
            cgz_this_error("{}:{}", m_redis->errstr, m_name);
            cgzRedisCloseCheck(&m_redis);
            return false;
        }
        else
        {
            freeReplyObject(reply);
        }
    }

    if (m_idb > 0)
    {
        redisReply *reply = (redisReply *)redisCommand(m_redis, "SELECT %d", m_idb);
        if (reply == nullptr)
        {
            cgz_this_error("{}:{}", m_redis->errstr, m_name);
            cgzRedisCloseCheck(&m_redis);
            return false;
        }
        else
        {
            freeReplyObject(reply);
        }
    }

    authret();
    return true;
}

bool cgzCHRedis::run(int64_t nms)
{
    bool no = false;
    while (!m_runTask.empty())
    {
        auto cmdTask = static_cast<cgzCRedisCmd *>(m_runTask.front());
        redisReply *reply = nullptr;

        bool ok = true;
        if (no)
        {
            ok = false;
            // 判断网络无法重连成功
        }
        else if (!m_redis)
        {
            ok = false;
            if (this->connect())
            {
                // 重新连接成功了
                cgz_this_error("Reconnected to Redis succ :{}", m_name);
                continue;
            }
            else
            {
                cgz_this_error("Reconnected to Redis fail :{}", m_name);
                no = true;
            }
        }
        else
        {
            // 认为是成功连接的
            const int cmd_idx = static_cast<int>(cmdTask->m_cmd);
            const char *headcmd = cmds[cmd_idx];
            size_t argC = 0;
            m_argv[argC] = const_cast<char *>(headcmd);
            m_argvlen[argC] = strlen(headcmd);
            argC++;
            for (auto &it : cmdTask->m_args)
            {
                m_argv[argC] = it.c_str();
                m_argvlen[argC] = it.size();
                argC++;
            }

            reply = (redisReply *)redisCommandArgv(m_redis, argC, m_argv, m_argvlen);
            if (reply == nullptr)
            {
                ok = false;
                if (m_redis->err == REDIS_ERR_IO ||
                    m_redis->err == REDIS_ERR_EOF ||
                    m_redis->err == REDIS_ERR_TIMEOUT)
                {
                    // 释放旧链接-然后尝试重新连接
                    cgzRedisCloseCheck(&m_redis);
                    continue;
                }
                else
                {
                    cgz_this_error("errnum:{} errstr:{} {}", m_redis->err, m_redis->errstr, m_name);
                }
            }
        }

        m_runTask.pop_front();
        if (cmdTask->m_ret)
        {
            // 结果回调
            cmdTask->m_err = !ok;
            cmdTask->m_reply = reply;
            cmdTask->origin = this->m_id;
            cmdTask->target = this->m_cid;
            cmdTask->m_code = cgzRedisCode::EXEC_REPLY;
            retTask(cmdTask);
        }
        else
        {
            // 是否请求
            cmdTask->m_reply = reply;
            cmdTask->~cgzCRedisCmd();
            free(cmdTask);
        }
    }

    return true;
}